package cn.chengpengper.seckill.order.application.place.impl;

import cn.chengpengper.dubbo.interfaces.goods.SeckillGoodsDubboService;
import cn.chengpengper.seckill.common.cache.distribute.DistributedCacheService;
import cn.chengpengper.seckill.common.code.HttpCode;
import cn.chengpengper.seckill.common.constants.SeckillConstants;
import cn.chengpengper.seckill.common.exception.SeckillException;
import cn.chengpengper.seckill.common.lock.DistributedLock;
import cn.chengpengper.seckill.common.lock.factory.DistributedLockFactory;
import cn.chengpengper.seckill.common.model.dto.SeckillGoodsDTO;
import cn.chengpengper.seckill.common.model.message.TxMessage;
import cn.chengpengper.seckill.order.application.model.command.SeckillOrderCommand;
import cn.chengpengper.seckill.order.application.place.SeckillPlaceOrderService;
import cn.chengpengper.seckill.order.domain.model.entity.SeckillOrder;
import cn.chengpengper.seckill.order.domain.service.SeckillOrderDomainService;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author chengpeng.hu
 */
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(name = "place.order.type", havingValue = "lock")
public class SeckillPlaceOrderLockServiceImpl implements SeckillPlaceOrderService {

    @DubboReference(version = "1.0.0")
    private SeckillGoodsDubboService seckillGoodsDubboService;

    private final SeckillOrderDomainService seckillOrderDomainService;

    private final DistributedLockFactory distributedLockFactory;

    private final DistributedCacheService distributedCacheService;

    @Override
    public void saveOrderInTransaction(TxMessage txMessage) {

    }

    @Override
    public Long placeOrder(Long userId, SeckillOrderCommand seckillOrderCommand) {
        // 获取商品
        SeckillGoodsDTO seckillGoods = seckillGoodsDubboService.getSeckillGoods(seckillOrderCommand.getGoodsId(), seckillOrderCommand.getVersion());
        // 校验
        checkSeckillOrder(seckillOrderCommand, seckillGoods);
        String lockKey = SeckillConstants.getKey(SeckillConstants.ORDER_LOCK_KEY_PREFIX, String.valueOf(seckillOrderCommand.getGoodsId()));
        DistributedLock lock = distributedLockFactory.getDistributedLock(lockKey);
        // 获取内存中的库存信息
        String key = SeckillConstants.getKey(SeckillConstants.GOODS_ITEM_STOCK_KEY_PREFIX, String.valueOf(seckillOrderCommand.getGoodsId()));
        boolean isDecrementCacheStock = false;
        try {
            if (!lock.tryLock(2, 5, TimeUnit.SECONDS)) {
                // 未获取到分布式锁
                throw new SeckillException(HttpCode.RETRY_LATER);
            }
            // 查询库存信息
            Integer stock = distributedCacheService.getObject(key, Integer.class);
            if(Objects.isNull(stock)){
                stock = seckillGoodsDubboService.getAvailableStockById(seckillOrderCommand.getGoodsId());
                distributedCacheService.put(key, stock);
            }
            if (stock < seckillOrderCommand.getQuantity()) {
                // 库存不足
                throw new SeckillException(HttpCode.STOCK_LT_ZERO);
            }
            // 扣减缓存中的库存
            distributedCacheService.decrement(key, seckillOrderCommand.getQuantity());
            // 标记扣减库存标记为true
            isDecrementCacheStock = true;
            // 构建订单
            SeckillOrder seckillOrder = buildSeckillOrder(userId, seckillOrderCommand, seckillGoods);
            // 保存订单
            seckillOrderDomainService.saveSeckillOrder(seckillOrder);
            // 扣减数据库库存
            seckillGoodsDubboService.decDbAvailableStock(seckillOrderCommand.getQuantity(), seckillOrderCommand.getGoodsId());
            return seckillOrder.getId();
        } catch (Exception e) {
            // 已经扣减了缓存中的库存，则需要增加回来
            if (isDecrementCacheStock) {
                log.error("SeckillPlaceOrderLockServiceImpl|下单发生异常，缓存中库存已扣除，增加回来");
                distributedCacheService.increment(key, seckillOrderCommand.getQuantity());
            }
            if (e instanceof InterruptedException) {
                log.error("SeckillPlaceOrderLockServiceImpl|下单分布式锁被中断|参数:{}|异常信息:{}", JSONObject.toJSONString(seckillOrderCommand), e.getMessage());
            } else {
                log.error("SeckillPlaceOrderLockService|下单失败|参数:{}|异常信息:{}", JSONObject.toJSONString(seckillOrderCommand), e.getMessage());
            }
            throw new SeckillException(e.getMessage());
        } finally {
            lock.unlock();
        }
    }
}
